package com.mashuai.utils.toolkit.core.msDataSource.register;

import com.mashuai.utils.base.MsAssertUtils;
import com.mashuai.utils.base.MsIocUtils;
import com.mashuai.utils.config.constants.ApplicationConstant;
import com.mashuai.utils.config.constants.StaticConstant;
import com.mashuai.utils.toolkit.inter.IMsRegisterDataSource;
import com.mashuai.utils.toolkit.core.msDataSource.dto.MsDataSourceMsgDto;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.*;
import org.springframework.core.type.AnnotatedTypeMetadata;

import javax.sql.DataSource;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 在ioc容器全部初始化结束后，在动态注入bean到ioc容器 SmartInitializingSingleton
 *
 * @Autowired
 * @ClassName: MsBeanRegister
 * @Description:
 */
@Configuration
public class MsRegisterDataSource {
    //数据源缓存
    private static Map<Object, Object> dataSourceCache = new ConcurrentHashMap<>();
    private final static String NO_DEFAULT_SOURCE_DATA = "使用到多数据源，但是没有配置默认的数据源/或者存在多个默认的数据源！请检查MsDataSourceMsgDto中的isDefault值是否存在多个true";
    private final static String NO_IMPL_REGISTER_INTERFACE = "已开启多数据源功能，但是并为未实现IMsRegisterDataSource接口，进行数据源配置功能！";
    private final static String NO_ADD_DEF_DATA_SOURCE = "MsDataSourceUtils.switchDataSource()方法不可以添加key=master的默认的数据源！";

    //项目启动时候，添加固定的的数据源信息
    @DependsOn(value = StaticConstant.DB_DEFAULT_GET_SOURCE_DATA_BEAN_NAME)
    @Primary
    @Conditional(MyCondition.class)
    @Bean(StaticConstant.DB_SWITCH_SOURCE_DATA_NAME)
    public MsSwitchDataSource setDataSourceCache() {
        addStaticDataSource();
        return new MsSwitchDataSource((DataSource) dataSourceCache.get(StaticConstant.DB_DEFAULT_SOURCE_DATA_NAME), dataSourceCache);
    }

    /**
     * 如果开启了多数据源配置，就注入此bean，默认不注入，如果值为true，那么就必须实现IMsRegisterDataSource接口，实现类需要加上@Qualifier("myDataSourceMsg")注解即可
     **/
    static class MyCondition extends SpringBootCondition {
        @Override
        public ConditionOutcome getMatchOutcome(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
            //获取配置文件的变量
            String enabled = conditionContext.getEnvironment().getProperty("ms.db.open-data-source");
            if (enabled != null && enabled.equalsIgnoreCase("true")) {
                return ConditionOutcome.match("MyProperty is enabled");
            } else {
                return ConditionOutcome.noMatch("MyProperty is not enabled");
            }
        }
    }

    /**
     * 添加固定的数据源信息
     **/
    public void addStaticDataSource() {
        //是否开启
        if ("true".equals(ApplicationConstant.msDbOpenDataSource)) {
            String[] beanNames = MsIocUtils.getBeanNames(IMsRegisterDataSource.class);
            if (beanNames.length == 2) {
                //获取用户处理好的数据源列表
                IMsRegisterDataSource registerDataSource = (IMsRegisterDataSource) MsIocUtils.getBean(beanNames[0], IMsRegisterDataSource.class);
                Map<String, MsDataSourceMsgDto> map = registerDataSource.getDataSourceMsg();
                //是否存在默认的数据源
                Integer defaultDataSourceCount = 0;
                //默认的数据源数据格式处理
                if (map != null && map.size() != 0) {
                    for (String key : map.keySet()) {
                        //获取map的每个数据源
                        MsDataSourceMsgDto dto = map.get(key);
                        DataSource source = handleDto(dto);
                        if (dto.getIsDefault() != null && dto.getIsDefault()) {
                            defaultDataSourceCount += 1;
                            //判断是否是主数据源，如果是主数据源强制key为master
                            dataSourceCache.put(StaticConstant.DB_DEFAULT_SOURCE_DATA_NAME, source);
                        }
                        dataSourceCache.put(key, source);

                    }
                } else {
                    MsAssertUtils.throwException("ms.db.open-data-source=true，在实现IMsRegisterDataSource接口的方法中必须返回一个主数据源链接信息");
                }
                if (defaultDataSourceCount != 1) MsAssertUtils.throwInnerException(NO_DEFAULT_SOURCE_DATA);
            } else {
                MsAssertUtils.throwInnerException(NO_IMPL_REGISTER_INTERFACE);
            }

        }
    }


    /**
     * 手动添加一个动态数据源，
     **/
    public static synchronized void addDataSources(String key, MsDataSourceMsgDto dto, boolean setDefDataSources) {
        //如果开启数据源配置，则允许添加，
        if ("true".equals(ApplicationConstant.msDbOpenDataSource)) {
            if (!setDefDataSources && StaticConstant.DB_DEFAULT_SOURCE_DATA_NAME.equals(key)) {
                MsAssertUtils.throwInnerException(NO_ADD_DEF_DATA_SOURCE);
            }
            DataSource source = handleDto(dto);
            dataSourceCache.put(key, source);
            refreshDataSources();
        }
    }


    /**
     * 手动移除一个数据源
     **/
    public static synchronized void removeDataSources(String key) {
        if ("true".equals(ApplicationConstant.msDbOpenDataSource) && dataSourceCache.containsKey(key)) {
            dataSourceCache.remove(key);
            refreshDataSources();
        }
    }

    /**
     * 获取某个数据源信息
     **/
    public static synchronized DataSource getDataSource(String key) {
        if (!"true".equals(ApplicationConstant.msDbOpenDataSource) || !dataSourceCache.containsKey(key)) {
            MsAssertUtils.throwInnerException("未开启多数据源配置或已配置的多数据源中不存在key为" + key + "的数据源信息");
        }
        return (DataSource) dataSourceCache.get(key);
    }


    /**
     * 数据源刷新
     **/
    public static void refreshDataSources() {
        MsSwitchDataSource switchDataSource = MsIocUtils.getBean(StaticConstant.DB_SWITCH_SOURCE_DATA_NAME);
        switchDataSource.setDataSource((DataSource) dataSourceCache.get(StaticConstant.DB_DEFAULT_SOURCE_DATA_NAME), dataSourceCache);
    }

    /**
     * 处理数据源链接
     **/
    private static DataSource handleDto(MsDataSourceMsgDto dto) {
        return DataSourceBuilder.create().driverClassName(dto.getDriverClassName()).url(dto.getJdbcUrl()).username(dto.getUsername()).password(dto.getPassword()).build();
    }
}
